{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a48fc8ee",
   "metadata": {},
   "source": [
    "## 自定义输出解析器\n",
    "\n",
    "在某些情况下，您可能希望实现自定义解析器以将模型输出构造为自定义格式。\n",
    "\n",
    "有两种方法可以实现自定义解析器：\n",
    "- 在 LCEL 中使用 RunnableLambda 或 RunnableGenerator – 我们强烈建议大多数用例使用此方法\n",
    "- 通过从基类之一继承进行解析——这是困难方法\n",
    "\n",
    "这两种方法之间的差异大多是表面的，主要在于触发哪些回调（例如， on_chain_start 与 on_parser_start ）\n",
    "\n",
    "### 可运行的 Lambda 和生成器\n",
    "\n",
    "推荐的解析方法是使用可运行的 lambda 和可运行的生成器！\n",
    "\n",
    "在这里，我们将进行一个简单的解析，反转模型输出的大小写。\n",
    "\n",
    "例如，如果模型输出：“Meow”，解析器将生成“mEOW”。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "e6dde69d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'hELLO! hOW CAN i HELP YOU TODAY?'"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from typing import Iterable\n",
    "from langchain_core.messages import AIMessage, AIMessageChunk\n",
    "\n",
    "from langchain_openai import ChatOpenAI, OpenAI\n",
    "openai_api_key = \"EMPTY\"\n",
    "openai_api_base = \"http://127.0.0.1:1234/v1\"\n",
    "model = ChatOpenAI(\n",
    "    openai_api_key=openai_api_key,\n",
    "    openai_api_base=openai_api_base,\n",
    "    temperature=0.3,\n",
    ")\n",
    "\n",
    "\n",
    "def parse(ai_message: AIMessage) -> str:\n",
    "    \"\"\"Parse the AI message.\"\"\"\n",
    "    return ai_message.content.swapcase()\n",
    "\n",
    "\n",
    "chain = model | parse\n",
    "chain.invoke(\"Hello\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "d64db155",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "oNCE UPON A TIME, IN THE HEART OF A BUSTLING CITY, THERE LIVED A YOUNG BOY NAMED jACK. dESPITE LIVING IN A FAST-PACED ENVIRONMENT FILLED WITH NOISE AND DISTRACTIONS, jACK HAD AN INSATIABLE CURIOSITY FOR NATURE AND WILDLIFE. hE WOULD OFTEN SPEND HIS WEEKENDS EXPLORING NEARBY PARKS AND FORESTS, OBSERVING BIRDS, INSECTS, AND OTHER CREATURES THAT CAUGHT HIS ATTENTION.\n",
      "\n",
      "oNE DAY, WHILE WANDERING THROUGH THE CITY'S LARGEST PARK, jACK STUMBLED UPON A SMALL, HIDDEN POND SURROUNDED BY LUSH GREENERY. iT WAS A SERENE OASIS IN THE MIDDLE OF THE CONCRETE JUNGLE, AND IT IMMEDIATELY CAPTURED HIS INTEREST. aS HE APPROACHED THE WATER'S EDGE, HE NOTICED SOMETHING UNUSUAL: A TINY FROG, NO BIGGER THAN A COIN, SITTING ON A LILY PAD. tHE LITTLE CREATURE SEEMED TO BE LOOKING BACK AT jACK WITH WIDE, CURIOUS EYES.\n",
      "\n",
      "jACK WAS FASCINATED BY THIS SMALL AMPHIBIAN AND DECIDED TO SIT DOWN NEARBY, OBSERVING IT IN SILENCE. hE WATCHED AS THE FROG LEAPED FROM ONE LILY PAD TO ANOTHER, ITS WEBBED FEET PROPELLING IT EFFORTLESSLY THROUGH THE WATER. tHE FROG SEEMED JUST AS INTRIGUED BY jACK'S PRESENCE, AND THEY SPENT HOURS TOGETHER, SHARING A MUTUAL FASCINATION FOR EACH OTHER.\n",
      "\n",
      "aS DAYS TURNED INTO WEEKS, jACK VISITED THE POND EVERY DAY AFTER SCHOOL, ALWAYS FINDING HIS LITTLE FRIEND WAITING PATIENTLY ON THE LILY PAD. tHEY FORMED AN UNSPOKEN BOND THAT TRANSCENDED WORDS, RELYING SOLELY ON THEIR SHARED CURIOSITY TO COMMUNICATE.\n",
      "\n",
      "oNE FATEFUL AFTERNOON, AS jACK APPROACHED THE POND, HE NOTICED A GROUP OF KIDS NEARBY, THROWING ROCKS AND STICKS INTO THE WATER. iN A PANIC, HE RUSHED TOWARDS THEM, PLEADING FOR THEM TO STOP. tHE CHILDREN IGNORED HIS CRIES AND CONTINUED THEIR DESTRUCTIVE GAME, UNAWARE OF THE FRAGILE ECOSYSTEM THEY WERE DISRUPTING.\n",
      "\n",
      "jACK KNEW THAT IF SOMETHING WASN'T DONE QUICKLY, HIS LITTLE FROG FRIEND WOULD BE IN DANGER. hE DECIDED TO TAKE ACTION, GATHERING AS MANY ROCKS AND STICKS AS HE COULD FIND AND BUILDING A MAKESHIFT BARRIER AROUND THE POND. tHE CHILDREN STOPPED THEIR DESTRUCTIVE BEHAVIOR AND WATCHED CURIOUSLY AS jACK WORKED TIRELESSLY TO PROTECT THE FRAGILE HABITAT.\n",
      "\n",
      "aS WORD SPREAD ABOUT jACK'S EFFORTS, MORE PEOPLE BEGAN VISITING THE HIDDEN OASIS, DRAWN BY ITS BEAUTY AND THE STORY OF THE YOUNG BOY WHO HAD DEDICATED HIMSELF TO PROTECTING IT. sOON, A LOCAL ENVIRONMENTAL GROUP GOT INVOLVED, HELPING TO CREATE AN OFFICIAL NATURE RESERVE AROUND THE POND.\n",
      "\n",
      "yEARS PASSED, AND jACK GREW UP, BUT HE NEVER FORGOT HIS LITTLE FROG FRIEND OR THE LESSONS HE LEARNED FROM THEIR SHARED CURIOSITY. hE WENT ON TO BECOME A RENOWNED BIOLOGIST, DEDICATING HIS LIFE TO UNDERSTANDING AND PRESERVING THE INTRICATE BALANCE OF ECOSYSTEMS. aND EVERY NOW AND THEN, WHEN HE FOUND HIMSELF IN NEED OF INSPIRATION, HE WOULD RETURN TO THAT HIDDEN POND, WHERE HE KNEW HIS TINY AMPHIBIAN FRIEND WAS STILL WAITING FOR HIM, JUST AS CURIOUS AS EVER."
     ]
    }
   ],
   "source": [
    "for chunk in chain.stream(\"tell me a story\"):\n",
    "    print(chunk, end=\"\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "07d36bc4",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.runnables import RunnableGenerator\n",
    "\n",
    "\n",
    "def streaming_parse(chunks: Iterable[AIMessageChunk]) -> Iterable[str]:\n",
    "    for chunk in chunks:\n",
    "        yield chunk.content.swapcase()\n",
    "\n",
    "\n",
    "streaming_parse = RunnableGenerator(streaming_parse)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "2329f08b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "oNCE UPON A TIME IN THE HEART OF aFRICA, THERE WAS A SMALL VILLAGE CALLED kIBURI. tHE VILLAGERS WERE SIMPLE PEOPLE WHO LIVED OFF THE LAND AND RELIED ON THEIR CROPS FOR SUSTENANCE. oNE DAY, A TERRIBLE DROUGHT STRUCK THE REGION, LEAVING THE FIELDS BARREN AND THE VILLAGERS DESPERATE FOR FOOD.\n",
      "\n",
      "tHE CHIEF OF kIBURI, mWAMBA, SUMMONED ALL THE ELDERS TO DISCUSS WHAT THEY SHOULD DO IN THIS DIRE SITUATION. aFTER MUCH DELIBERATION, IT WAS DECIDED THAT THEY WOULD SEND OUT A GROUP OF YOUNG MEN TO SEARCH FOR HELP FROM NEIGHBORING VILLAGES.\n",
      "\n",
      "aMONG THESE BRAVE YOUNG MEN WAS mWAMBA'S SON, kAMAU. hE WAS DETERMINED TO FIND FOOD AND SAVE HIS VILLAGE. tHE JOURNEY WAS LONG AND ARDUOUS, FILLED WITH MANY CHALLENGES AND OBSTACLES. bUT THE YOUNG MEN PERSEVERED, DRIVEN BY THEIR DESIRE TO HELP THEIR FAMILIES AND FRIENDS BACK IN kIBURI.\n",
      "\n",
      "aFTER WEEKS OF SEARCHING, THEY FINALLY FOUND A VILLAGE THAT WAS WILLING TO SHARE ITS RESOURCES WITH THEM. wITH GRATITUDE IN THEIR HEARTS, THE YOUNG MEN RETURNED HOME WITH SACKS FULL OF GRAIN AND SEEDS. tHE VILLAGERS REJOICED AT THIS UNEXPECTED BLESSING AND BEGAN PLANTING NEW CROPS, HOPING FOR A BOUNTIFUL HARVEST.\n",
      "\n",
      "aS TIME PASSED, kIBURI SLOWLY RECOVERED FROM THE DROUGHT. tHE VILLAGE FLOURISHED ONCE AGAIN, THANKS TO THE SELFLESS EFFORTS OF ITS PEOPLE. aND THOUGH THEY FACED MANY TRIALS ALONG THE WAY, THEIR UNWAVERING FAITH IN EACH OTHER AND THEIR DETERMINATION TO OVERCOME ADVERSITY ULTIMATELY LED THEM TO A BRIGHTER FUTURE."
     ]
    }
   ],
   "source": [
    "chain = model | streaming_parse\n",
    "for chunk in chain.stream(\"tell me a story\"):\n",
    "    print(chunk, end=\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "62213ef3",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ddbf6ea8",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
